package com.spukmk3me.util;

import com.spukmk3me.math.FPMath;

public final class DiscreteTimedAnimation
{
    public DiscreteTimedAnimation() {}
    
    /**
     *  @param frameSteps Frame steps, 16-16 static point.
     */
    public DiscreteTimedAnimation( int[] frameSteps )
    {
        setFrameSteps( frameSteps );
    }
    
    public final void update( int deltaTime )
    {
        if ( !m_animating )
            return;
            
        int deltaFrame = 0;
        
        if ( m_frameSteps == null )
            deltaFrame = FPMath.fpDiv( deltaTime, m_secPerFrame );
        else
            deltaFrame = calculateDeltaFrame( deltaTime );

        if ( m_direction )
            m_currentFrameIndex += deltaFrame;
        else
            m_currentFrameIndex -= deltaFrame;

        if ( m_frameStop )
        {
            m_remainingFrames -= deltaFrame;

            if ( m_remainingFrames <= 0 )
            {
                m_animating     = false;
                m_currentFrameIndex  = m_finishFrame << 16;
            }
        }

        int length_fp = m_lastIndex - m_firstIndex + 1 << 16;

        while ( FPMath.fpRound( m_currentFrameIndex ) < m_firstIndex )
            m_currentFrameIndex += length_fp;

        while ( FPMath.fpRound( m_currentFrameIndex ) > m_lastIndex )
            m_currentFrameIndex -= length_fp;            
    }
    
    public int getMode()
    {
        return m_mode;
    }
    
    public int getFirstIndex()
    {
        return FPMath.fpRound( m_firstIndex );
    }
    
    public int getLastIndex()
    {
        return FPMath.fpRound( m_lastIndex );
    }
    
    public void setFrameSteps( int[] frameSteps )
    {
        m_frameSteps = frameSteps;
    }
    
    public int[] getFrameSteps()
    {
        return m_frameSteps;
    }
    
    /**
     *  @return The current frame index.
     */
    public final int getFrameIndex()
    {
        return FPMath.fpRound( m_currentFrameIndex );
    }
    
    public final int getNextFrameIndex( int currentIndex, int number )
    {
        int nFrame = m_lastIndex - m_firstIndex + 1;
        
        if ( m_direction )
        {
            currentIndex += number;
            
            while ( currentIndex > m_lastIndex )
                currentIndex -= nFrame;
        }
        else
        {
            currentIndex -= number;
            
            while ( currentIndex < m_firstIndex )
                currentIndex += nFrame;
        }
        
        return currentIndex;
    }
    
    private int calculateDeltaFrame( int deltaTime )
    {
        int deltaFrame = 0;
        int frameIndex = getFrameIndex();
        int timeLeftThisFrame = FPMath.fpMul( m_frameSteps[ frameIndex ],
            0x00010000 - (m_currentFrameIndex & 0x0000FFFF) );
        
        if ( deltaTime < timeLeftThisFrame )
            return FPMath.fpDiv( deltaTime, m_frameSteps[ frameIndex ] );
        
        deltaFrame = 0x00010000 - (m_currentFrameIndex & 0x0000FFFF);
        deltaTime -= timeLeftThisFrame;
        frameIndex = getNextFrameIndex( frameIndex, 1 );
        
        while ( deltaTime >= m_frameSteps[ frameIndex ] )
        {
            deltaFrame += 0x00010000;
            deltaTime -= m_frameSteps[ frameIndex ];
            frameIndex = getNextFrameIndex( frameIndex, 1 );
        }
        
        if ( deltaTime > 0 )
            deltaFrame += FPMath.fpDiv( deltaTime, m_frameSteps[ frameIndex ] );
        
        return deltaFrame;
    }
    
    /**
     *  Set the frame index of this sprite.
     *  @param frameIndex The index of the frame.
     */
    public final void setFrameIndex( int frameIndex )
    {
        /* $if SPUKMK3ME_DEBUG$ */
        if ( m_frameSteps != null )
        {
            if ( (frameIndex >= m_frameSteps.length) || (frameIndex < 0) )
            {
                System.out.println( "Index was set out of range. Index: " +
                    frameIndex );
            }
        }
        /* $endif$ */
        
        m_currentFrameIndex = frameIndex << 16;
    }
    
    /**
     *  Setup the animation.
     *  @param mode The animating mode. Can be combined from MODE_BACKWARD,
     * MODE_FRAMESTOP, MODE_ANIMATING, MODE_AUTODROP.
     *  @param firstIndex The first frame index of animated frame sequence.
     *  @param lastFrame The last frame index of animated frame sequence.
     *  @param msPerFrame Amount of time for a frame, measured in milliseconds.
     * Pass -1 value to this parameter if you want to keep the old msPerFrame.
     *  @param nFrameToStop The animation will stop after nFrameToStop frames.
     * Ignored if the mode doesn't contain MODE_FRAMESTOP.
     */
    public final void setAnimating( int mode, int firstIndex, int lastIndex,
        int msPerFrame, int nFrameToStop )
    {
        m_mode = mode;
        m_animating = (mode & MODE_ANIMATING) != 0;
        m_direction = (mode & MODE_BACKWARD) == 0;
        m_frameStop = (mode & MODE_FRAMESTOP) != 0;

        if ( msPerFrame != -1 )
            m_secPerFrame = (msPerFrame << 16) / 1000;

        m_firstIndex    = firstIndex;
        m_lastIndex     = lastIndex;

        if ( m_frameStop )
        {
            m_remainingFrames = (nFrameToStop << 16) + 0x00007FFF;
            
            if ( m_direction )
            {
                m_finishFrame = (FPMath.fpRound( m_currentFrameIndex ) - firstIndex +
                    nFrameToStop) % (lastIndex - firstIndex + 1) + firstIndex;
            }
            else
            {
                m_finishFrame = lastIndex - ((lastIndex -
                    FPMath.fpRound( m_currentFrameIndex ) + nFrameToStop) %
                    (lastIndex - firstIndex + 1));
            }
        }
    }

    /**
     *  Check if the sprite is animating or not.
     *  @return true if this sprite is animating, otherwise return false.
     */
    public final boolean isAnimating()
    {
        return m_animating;
    }
    
    public final boolean getDirection()
    {
        return m_direction;
    }

    //! If the MODE_ANIMATING bit is zero, the animation won't animate.
    public static final byte    MODE_ANIMATING      = 0x01;
    //! If the MODE_BACKWARD bit is zero, forward animation is used.
    public static final byte    MODE_BACKWARD       = 0x02;
    //! Stop after pre-defined number of frames.
    public static final byte    MODE_FRAMESTOP      = 0x04;

    private int[]       m_frameSteps;
    private int         m_currentFrameIndex, m_remainingFrames, m_secPerFrame,
                        m_firstIndex, m_lastIndex, m_finishFrame, m_mode;
    private boolean     m_direction, m_animating, m_frameStop;
}
